﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Diagnostics;

namespace OpenStreetMapNetwork
{
    class Exception : System.Exception
    {
        public Exception() { }
        public Exception(string text) : base(text) { }
    }

    class Constants
    {
        public static int NODE_MAX = 10000;       // Maximum number of nodes
        public static int NAME_MAX = 10000;      // Maximum number of streets
        public static int SEGMENT_MAX = 10000;  // Maximum number of segments
    }

    class Node
    {
        public long id;
        public float lat;
        public float lon;

        public
        override String ToString()
        {
            return "Node[" + id + "] = (" + lat + "," + lon + ")";
        }

    }

    class NodeList
    {
        public int inod;          // node index in table
        public NodeList next;

        public NodeList()
        {
            inod = -1;
            next = null;
        }
    }

    class Segment
    {
        public NodeList first;
        public NodeList last;
        public long id;         // id segment
        public int name;        // index in name table

        public bool unidirectional;

        public Segment()
        {
            first = last = null;
            id = -1;
            name = -1;
            unidirectional = false;
        }

        public void AddNode(int index)
        {
            NodeList elem = new NodeList();
            elem.inod = index;

            if (first == null)
                first = last = elem;
            else
            {
                last.next = elem;
                last = elem;
            }
        }


        public
        override string ToString()
        {
            string r = "";
            NodeList c = first.next;

            while (c != null)
            {
                if (r.Length != 0)
                    r += ",";
                r += c.inod;

                c = c.next;
            }

            return "Segment[" + id + ", unic= " + unidirectional + "]=" + r;
        }
    }

    class Network
    {
        #region PRIVATE


        /**
         * Reads the value of the attribute and returns it.
         * */
        string ReadAttribute(XmlTextReader reader, string key)
        {
            for (int i = 0; i < reader.AttributeCount; ++i)
            {
                reader.MoveToAttribute(i);
                string attrName = reader.Name.ToLower();
                if (attrName.Equals(key))
                    return reader.Value;
            }

            throw new Exception(" Failed to find attribute by key: " + key);
        }

        long  ToId(string v)      { return long.Parse(v); }
        int   ToInteger(string v) { return int.Parse(v); }
        float ToReal(string v)    { return float.Parse(v); }

        /**
         * Reads the current node.
         * */
        void ReadNode(XmlTextReader reader)
        {
            // Check for space...
            if (!(numNodes < nodes.Length))
                throw new Exception("Maximum number of names (" + nodes.Length + ") has been exceeded");

            Node node = new Node();
            node.id = ToId(ReadAttribute(reader, "id"));
            node.lat = ToReal(ReadAttribute(reader, "lat"));
            node.lon = ToReal(ReadAttribute(reader, "lon"));

            // ... and add the node
            nodes[numNodes] = node;
            numNodes++;

        }

        /**
         * Returns the index of a name in the name table.
         * */
        int IndexOfName(string n)
        {
            for (int i = 0; i < numNames; ++i)
                if (names[i].Equals(n))
                    return i;

            if (!(numNames < names.Length))
                throw new Exception("Maximum number of names (" + names.Length + ") have been exceeded");

            names[numNames] = n;
            numNames++;
            return numNames - 1;
        }


        /**
         * Reads a new segment
         * */
        void ReadSegment(XmlTextReader reader)
        {
            

            Segment seg = new Segment();
            bool ok = true;
            bool isWay = false;
            string segmentName = null;

            seg.id = ToId(ReadAttribute(reader, "id"));

            ok = reader.Read();
            while (reader.NodeType != XmlNodeType.Element && reader.NodeType != XmlNodeType.EndElement)
                ok = reader.Read();

            while (ok && reader.IsStartElement())
            {
                string name = reader.Name.ToLower();

                if (name.Equals("nd"))
                {
                    long idnod = ToId(ReadAttribute(reader, "ref"));
                    seg.AddNode(IndexOfNode(idnod));
                }
                else if (name.Equals("tag"))
                {
                    string k = ReadAttribute(reader, "k").ToLower();
                    string v = ReadAttribute(reader, "v");

                    if (k.Equals("highway"))
                    {
                        isWay = true;
                    }
                    else if (k.Equals("name"))
                    {
                        segmentName = v;
                    }
                    else if (k.Equals("oneway") && v.Equals("yes"))
                    {
                        seg.unidirectional = true;
                    }
                }

                reader.Read();
                reader.MoveToElement();
            }

            // If it's not way then give up...
            if (!isWay)
                return;

            if (segmentName != null)
                seg.name = IndexOfName(segmentName);

            // .. else check for space and add the segment.
            if (!(numSegments < segments.Length))
                throw new Exception("Maximum number of segments(" + segments.Length + ") have been exceeded");

            segments[numSegments] = seg;
            numSegments++;

        }

        #endregion

        public Network()
        {
            nodes = new Node[Constants.NODE_MAX];
            numNodes = 0;

            names = new string[Constants.NAME_MAX];
            numNames = 0;

            segments = new Segment[Constants.SEGMENT_MAX];
            numSegments = 0;


        }

        // Load the network from XML
        public void Load(string src)
        {
            XmlTextReader reader = null;

            try
            {
                // Create reader
                reader = new XmlTextReader(src);

                while (reader.Read())
                {
                    if (reader.NodeType != XmlNodeType.Element)
                        continue;
                    string name = reader.Name.ToLower();

                    if (name.Equals("node"))
                    {
                        ReadNode(reader);
                    }
                    else if (name.Equals("way"))
                    {
                        ReadSegment(reader);
                    }
                }

            }
            finally
            {
                if (reader != null)
                    reader.Close();

                reader = null;
            }
        }

        public void Export(string dst)
        {

            StreamWriter writer = null;

            try
            {
                writer = new StreamWriter(dst);
                writer.WriteLine(numNodes + " " + numNames);

                for (int i = 0; i < numNodes; ++i)
                    writer.WriteLine(AccessNode(i).lat + " " + AccessNode(i).lon);

                for (int i = 0; i < numNames; ++i)
                {
                    writer.WriteLine(AccessName(i));
                }

                for (int i = 0; i < numSegments; ++i)
                {
                    Segment s = AccessSegment(i);

                    NodeList p = s.first;
                    if (p == null)
                    {
                        System.Console.WriteLine("Found segment without nodes ...");
                        continue;
                    }

                    NodeList c = p.next;

                    while (c != null)
                    {
                        writer.WriteLine(p.inod + " " + c.inod + " " + s.name);
                        if (!s.unidirectional)
                            writer.WriteLine(c.inod + " " + p.inod + " " + s.name);
                        p = c;
                        c = c.next;
                    }
                }

            }
            catch (IOException ex)
            {
                throw new Exception("Exception during exporting: " + ex.ToString());
            }
            finally
            {
                if (writer != null)
                    writer.Close();

                writer = null;
            }
        }

        /**
         * Returns index of node by id.
         * */
        public int IndexOfNode(long id)
        {
            for (int i = 0; i < numNodes; ++i)
                if (nodes[i].id == id)
                    return i;

            throw new Exception(" Failed to locate node with id: " + id);
        }

        private Node AccessNode(int index) { return nodes[index]; }
        private string AccessName(int index) { return names[index]; }
        private Segment AccessSegment(int index) { return segments[index]; }

        #region MEMBERS

        Node[] nodes;                  // Nodes table
        int numNodes;                  // Nodes number

        string[] names;                // Names table
        int numNames;                  // Names number

        Segment[] segments;            // Segment table
        int numSegments;               // Segment number
        #endregion

    }
}
